#include <iostream>
#include "debug.h"
#include "EventBoard.h"
#include "sdlScreen.h"
#include "sdlKeyboard.h"
#include "sdlTimer.h"
#include "particle.h"
#include <string>
//#include "sdlSprite.h"
#include "animatedDrawable.h"

//#include <aalib.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <math.h>

#define UNIFORM ((float)rand()/(float)RAND_MAX)
#define SCALE(x) (x*64.) // 64 pixels = 1 meter

int loadImg (std::string & filename, void** surface) {	 
   pDebug( "Loading" << filename );
   //The image that's loaded 
   SDL_Surface* loadedImage = NULL; 
   //The optimized image that will be used is SDL_Sprite::sprite
   //Load the image using SDL_image 
   loadedImage = IMG_Load( filename.c_str() ); 
   //If the image loaded
   if( loadedImage != NULL ) { //Create an optimized image 
      *surface = (void*) SDL_DisplayFormatAlpha( loadedImage ) ; 
      //*surface = (void*) SDL_DisplayFormat( loadedImage ) ; 
      //Free the old image 
      SDL_FreeSurface( loadedImage ); 
      return 0;
   } 
   return -1;
}

//
//------------------------------------------------------------------------------Fields
//

#define FIELD_ACTION( name ) void name(Particle* _part, PhysicsContext* _pc) 

#define GROUND_LEVEL 440.
void groundFieldAction(Particle* _part, PhysicsContext* _pc) {
   // This fiels prevents the particles from entering the ground at y = 400

   static PlaneConstraint plane(Vector(0.,-1.),Vector(0.,GROUND_LEVEL));

   if ( ( _part->pd->properties & PARTICLE_IS_SOLID ) != 0 ) {

      float h = (float) _part->getDrawable()->getGeometryRect()->h;
      float bottom = _part->pos.gety() + h;
      if ( bottom > GROUND_LEVEL ) {
	 _part->addConstraint( plane );
      }
   }
} 

FIELD_ACTION( earthGravityAction ) {
   if ( ( _part->pd->properties & PARTICLE_HAS_MASS ) != 0 ) {
      _part->acc.sety ( _part->acc.gety() + SCALE(1.) );
   }
}

//
//------------------------------------------------------------------------------ScreenBorder
//
// This is a lastig constraint because we don't want our character to leave the
// screen any time soon

class ScreenBorders : public Constraint {
   private:
      float w;
      float h;
   public:
      ScreenBorders( float _w, float _h ) :Constraint () {
	 toggleLasting();
	 ARG(w);
	 ARG(h);
      }

      virtual void honor ( Particle* );
};

void ScreenBorders::honor ( Particle* _particle) {
   Rect* geo = _particle->getDrawable()->getGeometryRect();
   Vector* vPos = &(_particle->pos);
   Vector* vVel = &(_particle->vel);
   float x = vPos->getx();
   float gw = geo->w;

   if (x < 0 ) {
      vPos->setx(0);
      vVel->setx(0);
   } else {
      if (x > w-gw ) {
	 vPos->setx(w-gw);
	 vVel->setx(0);
      }
   }
}
//
//------------------------------------------------------------------------------Character
//

enum CharMsg {
   CM_VOID, //is returned when there is not event
   CM_MOVE_LEFT,
   CM_MOVE_RIGHT,
   CM_JUMP,
};

#define CHAR_VEL SCALE(2.)
class Character : public Particle {
   public:
      EventBoard* eb;

      Character (  Vector const & _pos,
	  Vector const & _vel,		
	  Vector const & _acc,		
	  Sprite * _sprite, ParticleDescription* _pd );

      void stepLeft() {
	 if ( vel.getx() > 0 ) {
	    vel.setx(0);
	 } else {
	    vel.setx(-CHAR_VEL);
	 }
	 setFps();
      }
      void stepRight() {
	 if ( vel.getx() < 0 ) {
	    vel.setx(0);
	 } else {
	    vel.setx(CHAR_VEL);
	 }
	 setFps();
      }
      void jump() {
	 vel+=Vector(0.,-CHAR_VEL);
      }

      virtual void resetAcc() {
	 acc.set(0.,0.);
      }

      void setFps( );
      void actualize();
};

Character::Character (  Vector const & _pos,		//#D
	  Vector const & _vel,		
	  Vector const & _acc,		
	  Sprite * _sprite, ParticleDescription* _pd ) : Particle ( _pos, _vel, _acc, _sprite, _pd ){	
   eb = new EventBoard();
}

void Character::setFps( ) {	  //#D
   pd->setFps ( fabs(vel.getx()) * 0.25 );
}

void Character::actualize() {
   int event;
   while ( ( event= eb->read () ) != CM_VOID ){
      switch( event ) {
	 case CM_MOVE_LEFT : stepLeft(); break;
	 case CM_MOVE_RIGHT : stepRight(); break;
	 case CM_JUMP : jump(); break;
      }
   }
}

//
//----------------------------------------------------------------------------------Game
//

enum GameEvent {
   GM_VOID, //is returned when there is not event
   GM_QUIT,
   GM_RAIN,
   GM_PLAYER_MOVE_LEFT,
   GM_PLAYER_MOVE_RIGHT,
   GM_PLAYER_JUMP,
};

DrawableList * global_drawable_list = new DrawableList();
DrawableList* Screen::drawableList = global_drawable_list;

class Game {
   private:
      EventBoard     *eBoard;
      SDL_Keyboard   *kb;
      SDL_Screen     *screen;
      SDL_Timer	     *timer;

      ParticleEngine* engine;
      PhysicsContext* pc;

   //Sprites
      Sprite* asteroid;
      Sprite* background_sprite;
      AnimatedSprite* walkerSprite;
      Drawable* background;
      AnimatedDrawable* walker;
      Sprite* ground_sprite;
      Drawable* ground;

   //Fields
      Field* groundField;
      Field* earthGravity;

   //ParticleDescriptions
      ParticleDescription* asteroidPD;
      ParticleDescription* walkerPD;

   //Player
      Character* player;

   //Constraints
      ScreenBorders* screenBorders;

      void rain(); //makes the rain hapen

   public:
      Game ();
      void init ();
      void start ();
      void done ();
};

Game::Game () {		//#D
   eBoard   = new EventBoard ();
   screen   = new SDL_Screen (100,100);
   kb	    = new SDL_Keyboard ( eBoard );
   timer    = new SDL_Timer ();

   pc = new PhysicsContext ( 640., 480., 0.01, 50., timer, Vector(0.,0.) );
   engine = new ParticleEngine ( pc, global_drawable_list );
   
   std::string filename = "data/boulder.png";
   asteroid = new Sprite ( filename, loadImg, 32,32 );

   asteroidPD = new ParticleDescription ( PARTICLE_IS_SOLID | PARTICLE_HAS_MASS,16,8,8,1.);
   walkerPD = new ParticleDescription ( PARTICLE_HAS_MASS | PARTICLE_IS_SOLID | PARTICLE_HAS_FPS | PARTICLE_IS_UNELASTIC, 32,16,16,2.);
   walkerPD->setFps(0);

   filename = "data/backg.png";

   background_sprite = new Sprite ( filename, loadImg, 640,480 );

   background = new Drawable ( background_sprite );

   filename = "data/surface.png";

   ground_sprite = new Sprite ( filename, loadImg, 640,128 );

   ground = new Drawable ( ground_sprite );

   ground->getPosRect()->set(0,440,0,0);

   filename = "data/chart.png";

   walkerSprite = new AnimatedSprite( filename, loadImg ,4, 64, 64);

   walker = new AnimatedDrawable ( walkerSprite );

   walker->getPosRect()->set(200,200,0,0);

   groundField = new Field ( groundFieldAction );
   earthGravity = new Field ( earthGravityAction );
   screenBorders = new ScreenBorders(640,480);
}

void Game::init () {	   //#D

   /* initialize random seed: */
   srand ( time(NULL) );

   screen->init (SDL_INIT_VIDEO,640,480,8, SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_ANYFORMAT);
   walkerSprite->load ();
   asteroid->load();
   if ( background_sprite->load () == -1 ) {
      pDebug ("Cant load background.");
      exit(1);
   }

   ground_sprite->load();

   player = new Character( Vector(300,300),
	 Vector(0,0),
	 Vector(0,0),
	 walkerSprite, walkerPD
      );
      
   screen->setBackground( background );
   screen->drawableList->add ( ground );
   engine->add( *player );
   engine->addField ( *groundField );
   engine->addField ( *earthGravity );
   engine->addCollisionCheck( &solidCollision );
   player->addConstraint ( *screenBorders );

   screen->render ();
   screen->flush ();
   kb->init (NULL); 
   kb->bind( 'q', GM_QUIT );
   kb->bind( 'r', GM_RAIN );
   kb->bind( 'h', GM_PLAYER_MOVE_LEFT );
   kb->bind( 'l', GM_PLAYER_MOVE_RIGHT );
   kb->bind( 'k', GM_PLAYER_JUMP );
   
}

void Game::start () {
   int event;
   int quit=0;

   while ( quit==0 ) {
      kb->check ();
      event = eBoard->read ();
#define playerPost( msg ) player->eb->Post ( msg )
      switch(event) {
	 case GM_QUIT : quit++; break;
	 case GM_RAIN : rain(); break;
	 case GM_PLAYER_MOVE_LEFT : playerPost( CM_MOVE_LEFT ); break;
	 case GM_PLAYER_MOVE_RIGHT : playerPost( CM_MOVE_RIGHT ); break;
	 case GM_PLAYER_JUMP : playerPost( CM_JUMP ) ; break;
      }
#undef playerPost
      rain();
      player->actualize();
      engine->actualize();
      screen->render ();
      screen->flush ();
   }
}

void Game::done () {
   kb->done ();
   screen->done ();
}

void Game::rain() {
   UINT fps = 1500;
   static UINT start=0;
   int i;
   float threshold=0.9;

   if ( timer->getTicks() - start > fps ) {
      start = timer->getTicks();

      for(i=1;i<14;i++) {
	 if (UNIFORM > threshold) {
	    float angle = UNIFORM*3.14/2.+3.14/4.;
   
	    Particle* new_Asteroid = new Particle ( 
	       Vector ( 50*(float)i, -40 ), 
	       Vector(cos(angle),sin(angle))*80.,
	       Vector(0,0),
	       asteroid,
	       asteroidPD
	       );

	    engine->add ( *new_Asteroid );
	 }
      }
   }
}


int main () {	  
   Game game;

   game.init ();
   game.start ();
   game.done ();
   /*
   PhysicsContext pc ( 100., 100., 0.01 );
   Vector x( 1., 0. );
   Vector y( 0., 1. );
   Particle part ( x*10., y*10., x*0.); 
   ParticleEngine engine ( &pc );
   engine.add ( part );
   engine.actualize ();
   engine.actualize ();
   */
   return 0;
}
